fix(routing): expire stale pending-text + name session from its first message#140
Merged
Conversation
… message A message typed while no active session exists is stashed in ``context.user_data["_pending_text"]`` and forwarded once a session is created. ``user_data`` is in-memory and only a bot restart clears it, so a stash could survive for hours and then be injected into an unrelated session created much later. Field incident (2026-06-28): a 01:05 "почему смартфон на 100%" message was stashed (no active session after an overnight restart marked all windows lost), delivered to the resumed session via the auto-restore path — which never popped ``_pending_text`` — and then, with no further restart to clear memory, the stale stash was re-forwarded at 10:34 into a brand-new window. That window then caught the user's unrelated "найди сессию про глотание / альфа страховка" message and got auto-named "medical insurance". Fixes: - ``stash_pending_text`` / ``take_pending_text`` (TTL ``PENDING_TEXT_TTL_S`` = 10 min): consumption drops a stale stash and always clears the slot, so a leaked pending message can never re-fire. Tolerates the legacy bare-string format for state in flight across deploy. - ``_session_create`` and ``window_picker`` consume via ``take_pending_text`` (single clearing read) instead of get-then-pop. - ``_session_create`` seeds the auto-name from the forwarded pending text (the session's actual first human message), so a session is named by its first message rather than whatever inbound lands first. ``maybe_auto_name`` stays one-shot via its re-entrancy guard. Adds tests/test_pending_text_ttl.py. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…_text Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A message typed while no active session exists is stashed in
context.user_data["_pending_text"]and forwarded once a session is created.user_datais in-memory and only a bot restart clears it — so a stash can survive for hours and then be injected into an unrelated session created much later.Field incident (2026-06-28)
lost, so_resolve_active_windowfound no active session → opened the directory browser and stashed the text._pending_text._session_createforwards the leftover 01:05 text into a brand-new window, which then catches the user's unrelated «найди сессию про глотание / альфа страховка» message and gets auto-named medical insurance.Forensics confirmed in
bot.log: singleStarting Telegram botat 01:02,No active session: showing directory browserat 01:05:32, thenForwarding pending text … len=93at 10:34:26 with no intervening re-stash.Fix
stash_pending_text/take_pending_textinhandlers/directory_browser.py(TTLPENDING_TEXT_TTL_S= 10 min). Consumption drops a stale stash and always clears the slot, so a leaked pending message can never re-fire. Legacy bare-string format tolerated for state in flight across deploy._session_createandwindow_pickernow consume viatake_pending_text(one clearing read) instead of get-then-pop._session_createseeds the auto-name from the forwarded pending text (the session's real first human message) rather than whatever inbound lands first.maybe_auto_namestays one-shot via its re-entrancy guard.Tests
tests/test_pending_text_ttl.py— fresh round-trip, stale-drop, TTL boundary, custom max-age, no-expiry, legacy string, absent/empty, None-safe.🤖 Generated with Claude Code